package com.xiaoxin.experience.system;

import com.xiaoxin.experience.util.StringUtil;
import com.xiaoxin.experience.util.SystemUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.Properties;

/**
 * @author chenchaohao
 * @date 2019/11/26 15:26
 */
public class DASASEEncryptUtil
{
    private static final Logger LOG = LoggerFactory.getLogger(DASASEEncryptUtil.class);

    private static final String PKCS5PADDING_TRANSFORMATION = "AES/GCM/PKCS5Padding";

    private static final String KEY_ATHOS = "wH35F4KypR57wk/iAbL53w==";

    private static final String ALGORITHM_TAG = "#";

    private static final int RANDOM_LENGTH = 16;

    private static Properties properties;

    static
    {
        Resource resource = new ClassPathResource("ssl/ssl.properties");
        try
        {
            properties = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ignored)
        {}
    }

    public static String encrypt(String decryptedText)
    {
        try
        {
            String workingKey = properties.getProperty("secret-key.working");
            String key = getKey(workingKey);
            if (StringUtil.validate(key))
            {
                return encrypt(decryptedText, key);
            }
            else
            {
                String workingKeyEncrypted = properties.getProperty("secret.key");
                String workingKeyDecode = getKey(workingKeyEncrypted);
                return encryptData(decryptedText, workingKeyDecode);
            }
        }
        catch (Exception e)
        {
            LOG.error("encrypt content fail.", e);
            throw new IllegalArgumentException("decrypt content fail.", e);
        }
    }

    private static String encrypt(String decryptedText, String key)
        throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException, InvalidAlgorithmParameterException
    {
        String keyIv = getNextSecureRandom();
        return encryptData(decryptedText, key, keyIv).concat(ALGORITHM_TAG).concat(keyIv);
    }

    public static String encryptData(String decryptedText, String key)
    {
        byte[] keyBytes = Base64.getDecoder().decode(key);
        String messageEncode = "";
        SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
        try
        {
            Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] iv = cipher.getIV();
            assert iv.length == 12;
            byte[] encryptData = cipher.doFinal(decryptedText.getBytes());
            assert encryptData.length == decryptedText.getBytes().length + 16;
            byte[] messageByte = new byte[12 + decryptedText.getBytes().length + 16];
            System.arraycopy(iv, 0, messageByte, 0, 12);
            System.arraycopy(encryptData, 0, messageByte, 12, encryptData.length);
            messageEncode = Base64.getEncoder().encodeToString(messageByte);
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        return messageEncode;
    }

    public static String encryptData(String decryptedText, String key, String keyIv)
        throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
        InvalidKeyException, BadPaddingException, IllegalBlockSizeException
    {
        byte[] keyBytes = Base64.getDecoder().decode(key);
        SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
        byte[] ivBytes = Base64.getDecoder().decode(keyIv);
        GCMParameterSpec ips = new GCMParameterSpec(128, ivBytes);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ips);
        byte[] result = cipher.doFinal(decryptedText.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(result);
    }

    public static String decryptData(String encryptedText, String key)
    {
        String retMessage = "";
        Cipher cipher = null;
        byte[] message = Base64.getDecoder().decode(encryptedText);
        byte[] keyBye = Base64.getDecoder().decode(key);
        try
        {
            cipher = Cipher.getInstance(PKCS5PADDING_TRANSFORMATION);
            SecretKey secretKey = new SecretKeySpec(keyBye, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            GCMParameterSpec params = new GCMParameterSpec(128, message, 0, 12);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, params);
            byte[] decryptData = cipher.doFinal(message, 12, message.length - 12);
            retMessage = new String(decryptData);
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        return retMessage;
    }

    public static String decryptData(String encryptedText, String key, String keyIv)
        throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
        InvalidKeyException, BadPaddingException, IllegalBlockSizeException
    {
        byte[] keyBye = Base64.getDecoder().decode(key);

        Key secretKey = new SecretKeySpec(keyBye, "AES");

        byte[] ivBytes = Base64.getDecoder().decode(keyIv);
        GCMParameterSpec ips = new GCMParameterSpec(128, ivBytes);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ips);
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
        byte[] original = cipher.doFinal(encryptedBytes);
        return new String(original, StandardCharsets.UTF_8);
    }

    public static String decrypt(String encryptedText)
    {
        try
        {
            String workingKey = properties.getProperty("secret-key.working");
            String key = getKey(workingKey);
            if (StringUtil.validate(key))
            {
                if (encryptedText.indexOf(ALGORITHM_TAG) > 0)
                {
                    return decrypt(encryptedText, key);
                }
                else
                {
                    String workingKeyEncrypted = properties.getProperty("secret.key");
                    String workingKeyDecode = getKey(workingKeyEncrypted);
                    return decryptData(encryptedText, workingKeyDecode);
                }
            }
            else
            {
                String encryption = properties.getProperty("secret.key");
                return decryptData(encryptedText, encryption);
            }
        }
        catch (Exception e)
        {
            LOG.error("decrypt content fail.", e);
            throw new IllegalArgumentException("decrypt content fail.", e);
        }
    }

    private static String decrypt(String encryptedText, String key)
        throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException,
        BadPaddingException, InvalidAlgorithmParameterException
    {
        String[] split = encryptedText.split(ALGORITHM_TAG);
        String text = split[0];
        String keyIv = split[1];
        return decryptData(text, key, keyIv);
    }

    private static String getNextSecureRandom()
        throws NoSuchAlgorithmException
    {
        byte[] content = new byte[RANDOM_LENGTH];
        SecureRandom sr = SystemUtil.isLinux() ? SecureRandom.getInstance("NativePRNGNonBlocking") :
                SecureRandom.getInstanceStrong();
        sr.nextBytes(content);
        return Base64.getEncoder().encodeToString(content);
    }

    public static String getKey(String workingKey)
    {
        String iv = properties.getProperty("secret-key.iv");
        String salt = properties.getProperty("secret-key.salt");
        String secondComponent = properties.getProperty("key-porthos");
        String thirdComponent = properties.getProperty("key-aramis");
        return SecretKeyResolver.resolveSecretKey(iv,
            workingKey,
            salt,
            KEY_ATHOS,
            secondComponent,
            thirdComponent);
    }

}
